package com.hzb.erp.api.pc.lesson.service.impl;

import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzb.erp.api.pc.clazz.entity.Classroom;
import com.hzb.erp.api.pc.clazz.entity.Clazz;
import com.hzb.erp.api.pc.clazz.pojo.ClassStudentAddDTO;
import com.hzb.erp.api.pc.clazz.service.ClassStudentService;
import com.hzb.erp.api.pc.clazz.service.ClassroomService;
import com.hzb.erp.api.pc.clazz.service.ClazzService;
import com.hzb.erp.api.pc.course.entity.Course;
import com.hzb.erp.api.pc.course.entity.CourseTrialRecord;
import com.hzb.erp.api.pc.course.mapper.CourseTrialRecordMapper;
import com.hzb.erp.api.pc.course.service.CourseService;
import com.hzb.erp.api.pc.lesson.entity.*;
import com.hzb.erp.api.pc.lesson.mapper.LessonMapper;
import com.hzb.erp.api.pc.lesson.mapper.LessonScheduleMapper;
import com.hzb.erp.api.pc.lesson.mapper.LessonStudentMapper;
import com.hzb.erp.api.pc.lesson.pojo.*;
import com.hzb.erp.api.pc.lesson.service.*;
import com.hzb.erp.api.pc.student.entity.Student;
import com.hzb.erp.api.pc.student.entity.StudentCourse;
import com.hzb.erp.api.pc.student.service.StudentCourseService;
import com.hzb.erp.api.pc.student.service.StudentLessonCountLogService;
import com.hzb.erp.api.pc.student.service.StudentService;
import com.hzb.erp.api.pc.sys.entity.Holiday;
import com.hzb.erp.api.pc.sys.service.HolidayService;
import com.hzb.erp.api.pc.sys.service.MessageService;
import com.hzb.erp.api.pc.sys.service.NotificationService;
import com.hzb.erp.common.entity.Staff;
import com.hzb.erp.common.entity.StaffOrginfo;
import com.hzb.erp.common.enums.*;
import com.hzb.erp.common.mapper.StaffOrginfoMapper;
import com.hzb.erp.common.service.SettingService;
import com.hzb.erp.common.service.StaffService;
import com.hzb.erp.exception.BizException;
import com.hzb.erp.sysservice.ImportExportService;
import com.hzb.erp.sysservice.enums.SettingNameEnum;
import com.hzb.erp.sysservice.notification.NoticeCodeEnum;
import com.hzb.erp.sysservice.notification.bo.LessonChangeBO;
import com.hzb.erp.sysservice.notification.bo.LessonNotEnoughBO;
import com.hzb.erp.sysservice.notification.bo.LessonStartBO;
import com.hzb.erp.sysservice.notification.bo.StudentSignBO;
import com.hzb.erp.utils.DateTool;
import com.hzb.erp.utils.EnumTools;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 课次表 服务实现类
 * </p>
 *
 * @author 541720500@qq.com
 */
@Service
@Slf4j
public class LessonServiceImpl extends ServiceImpl<LessonMapper, Lesson> implements LessonService {

    @Autowired
    private LessonTeacherService lessonTeacherService;
    @Autowired
    @Lazy
    private NotificationService notificationService;
    @Autowired
    private ClassStudentService classStudentService;
    @Autowired
    private ClazzService clazzService;
    @Autowired
    private ClassroomService classroomService;
    @Autowired
    private StudentService studentService;
    @Autowired
    private StaffService staffService;
    @Autowired
    private LessonStudentService lessonStudentService;
    @Autowired
    private LessonStudentMapper lessonStudentMapper;
    @Autowired
    private CourseService courseService;
    @Autowired
    private StudentLessonCountLogService studentLessonCountLogService;
    @Resource
    private LessonMapper lessonMapper;
    @Autowired
    private ImportExportService importExportService;
    @Autowired
    private StudentCourseService studentCourseService;
    @Autowired
    private TeachEvaluationService teachEvaluationService;
    @Resource
    private LessonScheduleMapper lessonScheduleMapper;
    @Autowired
    private MessageService messageService;
    @Autowired
    private SettingService settingService;
    @Autowired
    private AppointmentService appointmentService;
    @Resource
    private StaffOrginfoMapper staffOrginfoMapper;
    @Autowired
    private HolidayService holidayService;
    @Autowired
    private CourseTrialRecordMapper courseTrialRecordMapper;

    @Override
    public IPage<LessonVO> getList(LessonParamDTO param) {
        this.makeCanLeaveTime(param);
        return this.baseMapper.getList(new Page<>(param.getPage(), param.getPageSize()), param);
    }

    @Override
    public List<LessonVO> getAll(LessonParamDTO param) {
        this.makeCanLeaveTime(param);
        return this.baseMapper.getList(param);
    }

    /**
    * 附带上可以请假的参数
    * */
    private void makeCanLeaveTime(LessonParamDTO param) {
        if(param.getCanLeaveTime() == null) {
            int limitHour = settingService.intValue(SettingNameEnum.STUDENT_LEAVE_LIMIT_HOUR.getCode());
            if(limitHour == 0) {
                param.setCanLeaveTime(LocalDateTime.now());
            } else {
                param.setCanLeaveTime(LocalDateTime.now().plusHours(limitHour));
            }
        }
    }

    @Override
    public List<Staff> getTeachers(Lesson lesson) {
        QueryWrapper<LessonTeacher> qw = new QueryWrapper<>();
        qw.eq("lesson_id", lesson.getId());
        List<LessonTeacher> list = lessonTeacherService.list(qw);
        if (list == null || list.size() == 0) {
            return null;
        }
        QueryWrapper<Staff> qw1 = new QueryWrapper<>();
        qw1.in("id", list.stream().map(LessonTeacher::getTeacherId).collect(Collectors.toList()));
        return staffService.list(qw1);
    }

    @Override
    public List<Student> getStudents(Lesson lesson) {
        List<Student> res = new ArrayList<>();
        List<Long> lessonStudentIds = lessonStudentMapper.getStudentIds(lesson.getId());
        if (lessonStudentIds == null || lessonStudentIds.size() == 0) {
            return res;
        }
        QueryWrapper<Student> qw1 = new QueryWrapper<>();
        qw1.in("id", lessonStudentIds);
        res = studentService.list(qw1);
        return res;
    }

    @Override
    public Boolean deleteBySchedule(List<Long> ids) {
        QueryWrapper<Lesson> qw = new QueryWrapper<>();
        qw.in("schedule_id", ids);
        // 删除签到记录
        List<Lesson> lessonList = list(qw);

        // 删除签到记录

        List<Long> lessonIds = lessonList.stream().map(Lesson::getId).collect(Collectors.toList());
        if(CollectionUtils.isNotEmpty(lessonIds)) {
            QueryWrapper<LessonStudent> qw1 = new QueryWrapper<>();
            qw1.in("lesson_id", lessonIds);
            lessonStudentService.remove(qw1);
            // 删除课次记录
            removeByIds(lessonIds);
        }

        // 更新排课计划为未生成
        for (Long id : ids) {
            LessonSchedule schedule = lessonScheduleMapper.selectById(id);
            schedule.setState(false);
            lessonScheduleMapper.updateById(schedule);
        }

        return true;
    }

    @Override
    public IPage<LessonTeacherStatsVO> statsByTeachers(LessonParamDTO param) {
        return this.baseMapper.statsByTeachers(new Page<>(param.getPage(), param.getPageSize()), param);
    }

    @Override
    public IPage<LessonCountDecreaseStatsVO> statisDecLesson(LessonParamDTO param) {
        return this.baseMapper.statisDecLesson(new Page<>(param.getPage(), param.getPageSize()), param);
    }

    @Override
    public Boolean deleteLesson(List<Long> ids) {
        return this.removeByIds(ids);
    }

    @Override
    public LessonVO getInfo(Long id) {
        LessonVO info = this.baseMapper.getInfo(id);
        LessonStudentParamDTO param = new LessonStudentParamDTO();
        param.setLessonId(id);
        info.setSignCounts(lessonStudentMapper.loadSignCounts(param));
        return info;
    }

    /**
    * 按班级新增和更新接口
    * */
    @Override
    @Transactional
    public Integer saveOrUpdateByDTO(LessonSaveDTO dto) {

        if (dto.getStartTime() == null || dto.getEndTime()==null || dto.getStartTime().isAfter(dto.getEndTime())) {
            throw new BizException("上课时间设置有误");
        }

        if(dto.getDate() == null ) {
            throw new BizException("未设置日期");
        }

        if(dto.getDecCount() == null || dto.getDecCount() < 1) {
            throw new BizException("消课课时设置错误");
        }

        // true为更新模式 false是新增模式
        boolean isUpdate = dto.getId() != null;
        Lesson oldLesson = null;
        if(isUpdate) {
            oldLesson = lessonMapper.selectById(dto.getId());
            // 修改不能修改班级，因为学生的签到记录有可能已经生成了
            dto.setClassId(oldLesson.getClassId());
        } else {
            if(dto.getClassId() == null ){
                throw new BizException("缺少参数classId");
            }
        }

        Long mainTeacherId = dto.getTeacherIds().get(0);
        LocalDate dt = dto.getDate();

        // 检查冲突
        LessonDatetimeAndTeacherBO bo = new LessonDatetimeAndTeacherBO();
        bo.setDate(dt);
        bo.setTeacherId(mainTeacherId);
        bo.setStartTime(dto.getStartTime());
        bo.setEndTime(dto.getEndTime());
        bo.setLessonId(dto.getId());
        checkConflictByDO(bo);

        Long schoolId = null;
        if(mainTeacherId!=null) {
            StaffOrginfo staffOrginfo = staffOrginfoMapper.getByStaffId(mainTeacherId);
            if(staffOrginfo==null) throw new BizException("上课老师未设置机构信息");
            schoolId = staffOrginfo.getComId();
        }

        lessonTeacherService.validateTeacherIds(dto.getTeacherIds(), dto.getAssistantIds());

        Clazz clazz = clazzService.getById(dto.getClassId());

        Lesson lesson = new Lesson();
        BeanUtils.copyProperties(dto, lesson);
        lesson.setDate(dt);
        lesson.setCourseId(clazz.getCourseId());
        lesson.setTeacherId(mainTeacherId);
        lesson.setBookable(dto.getBookable());
        if(schoolId!=null) {
            lesson.setSchoolId(schoolId);
        }
        this.saveOrUpdate(lesson);

        List<LessonTeacher> updatingTeacherList = this.updateTeacherList(lesson, clazz, dto.getTeacherIds(), dto.getAssistantIds(), isUpdate);

        // 开启批量编辑后续课次
        if(isUpdate && BooleanUtils.isTrue(dto.getBatchUpdate())) {
            this.handleBatchUpdateLesson(oldLesson, lesson, updatingTeacherList );
        }

        return 1;
    }

    // 开启循环生成
    @Override
    @Transactional
    public Integer batchSaveByDTO(LessonBatchSaveDTO dto) {

        lessonTeacherService.validateTeacherIds(dto.getTeacherIds(), dto.getAssistantIds());
        Long mainTeacherId = dto.getTeacherIds().get(0);

        Long schoolId = null;
        if(mainTeacherId!=null) {
            StaffOrginfo staffOrginfo = staffOrginfoMapper.getByStaffId(mainTeacherId);
            if(staffOrginfo==null) throw new BizException("上课老师未设置机构信息");
            schoolId = staffOrginfo.getComId();
        }

        List<Lesson> readyList = new ArrayList<>();
        Clazz clazz = null;
        Course course = null;
        if("loop".equals(dto.getType())) {

            if(dto.getClassId() == null ){
                throw new BizException("未选择班级");
            }

            clazz = clazzService.getById(dto.getClassId());
            if(clazz.getCourseId()!=null) {
                course = courseService.getById(clazz.getCourseId());
            }

            if(dto.getWeekSetting() == null || dto.getWeekSetting().size() == 0) {
                throw new BizException("未设置上课周期和上课时间");
            }

            if(dto.getEndDate() == null || !dto.getDate().isBefore(dto.getEndDate())) {
                throw new BizException("结束日期设置有误");
            }

            for(LessonBatchSaveDTO.WeekSetting ws : dto.getWeekSetting()) {
                if(ws.getWeekDays() == null || ws.getWeekDays().length== 0) {
                    throw new BizException("星期设置错误");
                }
                if(ws.getStartTime() == null) {
                    throw new BizException("未设置开始时间");
                }
                if(ws.getEndTime() == null) {
                    throw new BizException("未设置结束时间");
                }
                if(ws.getDecCount() == null || ws.getDecCount() < 1) {
                    throw new BizException("消课课时设置错误");
                }

                if(ws.getStartTime().isAfter(ws.getEndTime())) {
                    throw new BizException("开始时间不能晚于结束时间");
                }

                List<LocalDate> dates = lessonOpenDates(dto.getDate(), dto.getEndDate(), Arrays.asList(ws.getWeekDays()), dto.getNoholidy() != null && dto.getNoholidy());
                if(CollectionUtils.isEmpty(dates)) {
                    throw new BizException("在起始日期内无有效日期");
                }

                if(dates.size() > 100) {
                    throw new BizException("排课次数过多");
                }

                for (LocalDate dt : dates ) {
                    Lesson newLesson = new Lesson();
                    BeanUtils.copyProperties(dto, newLesson);
                    newLesson.setTitle(course==null ? dt.toString()+"的课" : course.getName());
                    newLesson.setDate(dt);
                    newLesson.setStartTime(ws.getStartTime());
                    newLesson.setEndTime(ws.getEndTime());
                    newLesson.setDecCount(ws.getDecCount());
                    newLesson.setCourseId(clazz.getCourseId());
                    newLesson.setTeacherId(mainTeacherId);
                    newLesson.setBookable(dto.getBookable());
                    if(schoolId!=null) {
                        newLesson.setSchoolId(schoolId);
                    }

                    readyList.add(newLesson);
                }
            }
        } else if("freedom".equals(dto.getType())) {
            if(dto.getClassId() == null ){
                throw new BizException("未选择班级");
            }
            clazz = clazzService.getById(dto.getClassId());
            if(clazz.getCourseId()!=null) {
                course = courseService.getById(clazz.getCourseId());
            }

            if(dto.getDateList() == null || dto.getDateList().size() == 0) {
                throw new BizException("未设置日期");
            }

            if (dto.getStartTime() == null || dto.getEndTime()==null || dto.getStartTime().isAfter(dto.getEndTime())) {
                throw new BizException("上课时间设置有误");
            }

            if(dto.getDecCount() == null || dto.getDecCount() < 1) {
                throw new BizException("消课课时设置错误");
            }

            for (LocalDate dt : dto.getDateList() ) {
                Lesson newLesson = new Lesson();
                BeanUtils.copyProperties(dto, newLesson);
                newLesson.setTitle(course==null ? dt.toString()+"的课" : course.getName());
                newLesson.setDate(dt);
                newLesson.setStartTime(dto.getStartTime());
                newLesson.setEndTime(dto.getEndTime());
                newLesson.setDecCount(dto.getDecCount());
                newLesson.setCourseId(clazz.getCourseId());
                newLesson.setTeacherId(mainTeacherId);
                newLesson.setBookable(dto.getBookable());
                if(schoolId!=null) {
                    newLesson.setSchoolId(schoolId);
                }

                readyList.add(newLesson);
            }
        } else if("vip".equals(dto.getType())) {

            // 一对一排课
            if(dto.getStudentIds() == null || dto.getStudentIds().size() == 0) {
                throw new BizException("未选择学生");
            }
            Long studentId = dto.getStudentIds().get(0);

            if(dto.getCourseId() == null) {
                throw new BizException("未选择课程");
            }

            course = courseService.getById(dto.getCourseId());

            if(dto.getDateList() == null || dto.getDateList().size() == 0) {
                throw new BizException("未设置日期");
            }

            if (dto.getStartTime() == null || dto.getEndTime()==null || dto.getStartTime().isAfter(dto.getEndTime())) {
                throw new BizException("上课时间设置有误");
            }

            if(dto.getDecCount() == null || dto.getDecCount() < 1) {
                throw new BizException("消课课时设置错误");
            }

            Student student = studentService.getById(studentId);
            clazz = clazzService.autoCreateOne2One(student, course, mainTeacherId);

            for (LocalDate dt : dto.getDateList() ) {
                Lesson newLesson = new Lesson();
                BeanUtils.copyProperties(dto, newLesson);
                newLesson.setTitle(course==null ? dt.toString()+"的课" : course.getName());
                newLesson.setDate(dt);
                newLesson.setStartTime(dto.getStartTime());
                newLesson.setEndTime(dto.getEndTime());
                newLesson.setDecCount(dto.getDecCount());
                newLesson.setCourseId(dto.getCourseId());
                newLesson.setTeacherId(mainTeacherId);
                newLesson.setBookable(dto.getBookable());
                newLesson.setClassId(clazz.getId());
                if(schoolId!=null) {
                    newLesson.setSchoolId(schoolId);
                }

                readyList.add(newLesson);
            }

        }


        if(readyList.size() == 0) {
            throw new BizException("未设置上课日期");
        }

//        // 检查冲突
//        for (LocalDate dt : dates) {
//            LessonDatetimeAndTeacherBO bo = new LessonDatetimeAndTeacherBO();
//            bo.setDate(dt);
//            bo.setTeacherId(mainTeacherId);
//            bo.setStartTime(dto.getStartTime());
//            bo.setEndTime(dto.getEndTime());
//            bo.setLessonId(dto.getId());
//            checkConflictByDO(bo);
//        }

        this.saveBatch(readyList);

        for(Lesson ls : readyList) {
            this.updateTeacherList(ls, clazz, dto.getTeacherIds(), dto.getAssistantIds(), false);
        }

        return readyList.size();
    }

    /**
    * 更新老师列表数据
    * */
    private List<LessonTeacher> updateTeacherList(Lesson lesson, Clazz clazz, List<Long> teacherIds, List<Long> assistantIds, boolean isUpdate) {

        List<LessonTeacher> updatingTeacherList;

        // 如果是更新操作，先清空老师关联记录
        if (isUpdate) {
            lessonTeacherService.deleteByLessonId(lesson.getId(), null);
        }
        // 再保存一下老师分表
        List<LessonTeacher> teacherList = new ArrayList<>();
        for (Long teacherId : teacherIds) {
            LessonTeacher lt = new LessonTeacher();
            lt.setTeacherId(teacherId);
            lt.setLessonId(lesson.getId());
            lt.setTypeNum(TeacherTypeEnum.TEACHER.getCode());
            teacherList.add(lt);
        }
        if (assistantIds != null) {
            for (Long teacherId : assistantIds) {
                LessonTeacher lt = new LessonTeacher();
                lt.setTeacherId(teacherId);
                lt.setLessonId(lesson.getId());
                lt.setTypeNum(TeacherTypeEnum.ASSISTANT.getCode());
                teacherList.add(lt);
            }
        }
        lessonTeacherService.saveBatch(teacherList);
        updatingTeacherList = teacherList;

        if (!isUpdate) {
            // 如果是新增生成学生记录
            List<Student> studentList = classStudentService.getStudentsByClassId(lesson.getClassId());
            if(studentList != null) {
                List<LessonStudent> lsList = new ArrayList<>();
                for (Student student : studentList) {
                    LessonStudent newLs = LessonStudentServiceImpl.buildDefaultEntity(lesson, student, clazz);
                    lsList.add(newLs);
                }
                lessonStudentService.saveBatch(lsList);
            }
        } else {
            // 如果是更新，那么更新一下未签到的上课记录的应扣课时数，比如原来该课扣1课时的改成2课时的时候，未签到的上课记录都变成2课时，以后消课都按2课时了。
            LambdaUpdateWrapper<LessonStudent> wrapper = Wrappers.lambdaUpdate();
            wrapper.eq(LessonStudent::getLessonId, lesson.getId());
            wrapper.eq(LessonStudent::getSignState, SignStateEnum.NONE.getCode());
            wrapper.set(LessonStudent::getLessonCount, lesson.getDecCount());
            lessonStudentService.update(wrapper);
        }

        return updatingTeacherList;
    }

    // 批量更新后续的课时
    private void handleBatchUpdateLesson(Lesson oldLesson, Lesson newLesson, List<LessonTeacher> updatingTeacherList) {

        LambdaQueryWrapper<Lesson> wrapper = Wrappers.lambdaQuery();
        wrapper.gt(Lesson::getDate, oldLesson.getDate());
        wrapper.eq(Lesson::getStartTime, oldLesson.getStartTime());
        wrapper.eq(Lesson::getEndTime, oldLesson.getEndTime());
        wrapper.eq(Lesson::getState, LessonStateEnum.UNDERWAY);
        wrapper.eq(Lesson::getClassId, oldLesson.getClassId());
        wrapper.ne(Lesson::getId, oldLesson.getId());
        List<Lesson> fetchList = lessonMapper.selectList(wrapper);

        if(CollectionUtils.isEmpty(fetchList)) {
            return;
        }
        List<LessonTeacher> updatingTeachers = new ArrayList<>();
        for (Lesson fetchOne: fetchList) {
            fetchOne.setStartTime(newLesson.getStartTime());
            fetchOne.setEndTime(newLesson.getEndTime());
            fetchOne.setTeacherId(newLesson.getTeacherId());
//            fetchOne.setCourseId(newLesson.getCourseId());
//            fetchOne.setRoomId(newLesson.getRoomId());
//            fetchOne.setDecCount(newLesson.getDecCount());
//            fetchOne.setBookable(newLesson.getBookable());

            lessonTeacherService.deleteByLessonId(fetchOne.getId(), null);

            for (LessonTeacher lt : updatingTeacherList) {
                LessonTeacher newLT = new LessonTeacher();
                BeanUtils.copyProperties(lt, newLT);
                newLT.setId(null);
                newLT.setLessonId(fetchOne.getId());
                updatingTeachers.add(newLT);
            }
        }
        this.updateBatchById(fetchList);
        lessonTeacherService.saveBatch(updatingTeachers);

    }

    @Override
    public Integer createQuickly(LessonSaveQuicklyDTO dto, Long creatorId) {

        if (dto.getStartTime().isAfter(dto.getEndTime())) {
            throw new BizException("上课时间设置有误");
        }
        List<LocalDate> dates = new ArrayList<>();
        dates.add(dto.getDate());

        Course course = courseService.getById(dto.getCourseId());

        // 开启循环
        if(dto.getLoop() != null && dto.getLoop()) {
            if(dto.getEndDate() == null || !dto.getDate().isBefore(dto.getEndDate())) {
                throw new BizException("结束日期设置有误");
            }

            if(dto.getWeekDays() == null || dto.getWeekDays().length == 0) {
                throw new BizException("请选择周几上课");
            }

            dates = lessonOpenDates(dto.getDate(), dto.getEndDate(), Arrays.asList(dto.getWeekDays()), dto.getNoholidy() != null && dto.getNoholidy());
            if(CollectionUtils.isEmpty(dates)) {
                throw new BizException("在起始日期内无有效日期");
            }
        }

        if(dates.size() > 50) {
            throw new BizException("为提升用户体验，一次最多可生成100次课");
        }

        List<Long> teacherIds = dto.getTeacherIds();

        // 检查冲突
        for (LocalDate dt : dates) {
            LessonDatetimeAndTeacherBO bo = new LessonDatetimeAndTeacherBO();
            bo.setDate(dt);
            bo.setTeacherId(teacherIds.get(0));
            bo.setStartTime(dto.getStartTime());
            bo.setEndTime(dto.getEndTime());
            bo.setLessonId(dto.getId());
            checkConflictByDO(bo);
        }

        // 创建班级
        Clazz clazz = new Clazz();
        clazz.setName(course.getName() + " *");
        clazz.setCourseId(dto.getCourseId());
        clazz.setClassroomId(dto.getRoomId());
        clazz.setTeacherId(teacherIds.get(0));
        clazz.setBeOver(false);
        clazz.setOverTime(dto.getDate().atTime(23, 59));
        clazz.setStartDate(dto.getDate());
        clazz.setEndDate(dto.getDate());
        clazz.setPlannedLessonCount(1);
        clazz.setPlannedStudentCount(dto.getStudentIds().size());
        clazzService.save(clazz);

        // 创建班级的学生
        ClassStudentAddDTO classStudentAddDTO = new ClassStudentAddDTO();
        classStudentAddDTO.setClassId(clazz.getId());
        classStudentAddDTO.setStudentIds(dto.getStudentIds());
        classStudentService.addClassStudents(classStudentAddDTO, creatorId);

        // 支持不通过计划直接循环排课 202209
        for (LocalDate date : dates) {
            // 添加课程
            LessonSaveDTO lessonSaveDTO = new LessonSaveDTO();
            lessonSaveDTO.setTitle(course.getName());
            lessonSaveDTO.setTeacherIds(dto.getTeacherIds());
            lessonSaveDTO.setAssistantIds(dto.getAssistantIds());
            lessonSaveDTO.setClassId(clazz.getId());
            lessonSaveDTO.setRoomId(dto.getRoomId());
            lessonSaveDTO.setDate(date);
            lessonSaveDTO.setDecCount(dto.getDecCount());
            lessonSaveDTO.setStartTime(dto.getStartTime());
            lessonSaveDTO.setEndTime(dto.getEndTime());
            lessonSaveDTO.setBookable(dto.getBookable());
            saveOrUpdateByDTO(lessonSaveDTO);
        }
        return dates.size();

    }

    /**
     * 检查一个计划的冲突 返回发生冲突的lesson id列表
     *
     * @return Integer 冲突的数量
     */
    private void checkConflictByDO(LessonDatetimeAndTeacherBO bo) {
        List<Lesson> checked = lessonScheduleMapper.checkConflict(bo);
        if (CollectionUtils.isEmpty(checked)) {
            return;
        }

        StringBuilder conflictString = new StringBuilder();
        for(Lesson les : checked) {
            conflictString.append(les.getTeacherName())
                    .append(les.getDate().format(DateTimeFormatter.ofPattern("MM-dd")))
                    .append(" ")
                    .append(les.getStartTime()).append("; ");
        }

        if(StringUtils.isNotBlank(conflictString.toString())) {
            throw new BizException("与下列课程冲突：" + conflictString );
        }

    }

    /**
     * description
     * @param startDate
     * @param endDate
     * @param weekDays 周几上课
     * @param excludeHoliday 跳过节假日
     * @return
     */
    private List<LocalDate> lessonOpenDates(LocalDate startDate, LocalDate endDate, List<Integer> weekDays, boolean excludeHoliday) {
        List<LocalDate> everyDates = DateTool.findEveryDates(startDate, endDate);
        List<Holiday> holidays = holidayService.list();
        List<LocalDate> dates = new ArrayList<>();
        for (LocalDate date : everyDates) {

            // 如果星期里包含则追加
            if (weekDays.contains(date.getDayOfWeek().getValue())) {
                // 如果不排除假日 直接追加;
                if (!excludeHoliday) {
                    dates.add(date);
                } else {
                    // 如果开启了排除假日 且 不是节日则追加;
                    if (!isHoliday(date, holidays)) {
                        dates.add(date);
                    }
                }
            }
        }

        return dates;
    }

    private Boolean isHoliday(LocalDate date, List<Holiday> holidays) {
        if (date == null || holidays == null || holidays.size() == 0) {
            return false;
        }
        for (Holiday day : holidays) {
            if (day.getDate().isEqual(date)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean stopLesson(List<Long> ids) {
        List<Lesson> list = this.listByIds(ids);
        for (Lesson item : list) {
            item.setState(LessonStateEnum.STOPPED);
            sendLessonStateChangeNotice(item, LessonStateEnum.STOPPED);
        }
        return this.updateBatchById(list);
    }

    @Override
    public Boolean reopenLesson(List<Long> ids) {
        List<Lesson> list = this.listByIds(ids);
        for (Lesson item : list) {
            item.setState(LessonStateEnum.UNDERWAY);
            sendLessonStateChangeNotice(item, LessonStateEnum.UNDERWAY);
        }
        return this.updateBatchById(list);
    }

    /**
     * 发送调课通知
     */
    private void sendLessonStateChangeNotice(Lesson lesson, LessonStateEnum state) {

        List<Student> students = getStudents(lesson);
        List<Staff> teachers = getTeachers(lesson);

        LessonChangeBO bo = new LessonChangeBO();
        bo.setLessonTitle(lesson.getTitle());
        bo.setDate(String.valueOf(lesson.getDate()));
        bo.setStartTime(String.valueOf(lesson.getStartTime()));
        bo.setEndTime(String.valueOf(lesson.getEndTime()));
        bo.setNewState(state.getName());
        bo.setSubject("调课通知：" + state.getName());

        if (students != null && students.size() > 0) {
            for (Student s : students) {
                bo.setStudentName(s.getName());
                bo.setTeacherName("");
                bo.setContent(s.getName() + "同学：你的课程《" + lesson.getTitle() + "》状态变为：" + state.getName());
                notificationService.sendToStudent(NoticeCodeEnum.STUDENT_LESSON_ONCHANGE, bo, s);
            }
        }
        if (teachers != null && teachers.size() > 0) {
            for (Staff t : teachers) {
                bo.setStudentName("");
                bo.setTeacherName(t.getName());
                bo.setContent(t.getName() + "老师：课程《" + lesson.getTitle() + "》状态变为" + state.getName());
                notificationService.sendToTeacher(NoticeCodeEnum.TEACHER_LESSON_ONCHANGE, bo, t);
            }
        }

    }

    /**
     * 学生签到
     */
    @Override
    public Boolean studentSign(Long lessonId, Long studentId, SignStateEnum state) {
        Lesson lesson = this.getById(lessonId);
        LessonStudent res = lessonStudentService.addRecord(lessonId, studentId, lesson, state, SignTypeEnum.STUDENT, null);
        // 课次变动日志
        if (res.getDecLessonCount() > 0) {
            studentLessonCountLogService.addOneByLesson(res, null);
            sendLessonSignNotice(lesson, res, studentId);
        }
        return true;
    }

    /**
     * 老师点名逻辑
     */
    @Override
    public Boolean rollCallBatch(Long teacherId, List<LessonSignSaveDTO> signData) {

        for (LessonSignSaveDTO item : signData) {
            Lesson lesson = this.getById(item.getLessonId());
            SignStateEnum state = EnumTools.getByCode(item.getState(), SignStateEnum.class);

            // 检查老师签到是否超过时间限制
            this.checkTeacherSignLimit(lesson, state, true);

            LessonStudent res = lessonStudentService.addRecord(item.getLessonId(), item.getStudentId(), lesson, state, SignTypeEnum.TEACHER, teacherId);
            if (state != null && res.getDecLessonCount() > 0) {
                // 课次变动日志
                studentLessonCountLogService.addOneByLesson(res, teacherId);
                lesson.setDecCount(res.getDecLessonCount()); // 实际消课基数次数
                sendLessonSignNotice(lesson, res, item.getStudentId());
            }
        }
        return true;
    }

    /**
     * 发送点名签到消息
     */
    private void sendLessonSignNotice(Lesson lesson, LessonStudent lessonStudent, Long studentId) {

        // 课程的id应该是消课记录里的，这个是准确的
        Long courseId = lessonStudent.getConsumeCourseId();
        Integer decLessonCount = lessonStudent.getDecLessonCount();
        Course course = courseService.getById(courseId);
        Student student = studentService.getById(studentId);
        int countRemaining = studentCourseService.listLessonRemainingCountTotal(courseId, studentId);

        StudentSignBO bo = new StudentSignBO();
        bo.setSubject("本次消课:"+ decLessonCount + ",剩余:" + countRemaining);
        bo.setLessonTitle(lesson.getTitle());
        bo.setDate(lesson.getDate().toString());
        bo.setTime(lesson.getStartTime().toString());
        bo.setStudentName(student.getName());
        bo.setContent(student.getName() + "同学：你的课程《" + course.getName() + "》已签到");
        bo.setState(lessonStudent.getSignState() == null ? "无": lessonStudent.getSignState().getName()); // 理论上这里并不会有null的可能性

        notificationService.sendToStudent(NoticeCodeEnum.STUDENT_SIGN, bo, student);
    }
//    /**
//     * 发送点名签到消息
//     */
//    private void sendLessonSignNotice(Lesson lesson, SignStateEnum state, Long studentId) {
//
//        Student student = studentService.getById(studentId);
//        int countRemaining = studentCourseService.listLessonRemainingCountTotal(lesson.getCourseId(), studentId);
//
//        StudentSignBO bo = new StudentSignBO();
//        bo.setSubject("签到通知");
//        bo.setLessonTitle(lesson.getTitle());
//        bo.setDate(lesson.getDate().toString());
//        bo.setTime(lesson.getStartTime().toString());
//        bo.setStudentName(student.getName());
//        bo.setContent(student.getName() + "同学：你的课程《" + lesson.getTitle() + "》已签到");
//        bo.setState(state.getDist() + ",本次消课数:"+ lesson.getDecCount() + ",剩余课时:" + countRemaining);
//
//        notificationService.sendToStudent(NoticeCodeEnum.STUDENT_SIGN, bo, student);
//
//    }

    @Override
    public SignStateEnum getSignStateByNow(Long lessonId) {
        // 学生是否可以签到
        boolean canSign = BooleanUtil.isTrue(settingService.boolValue(SettingNameEnum.STUDENT_CENTER_CAN_SIGN.getCode()));

        if (!canSign) {
            throw new BizException("学生无签到权限");
        }

        Lesson lesson = this.getById(lessonId);
        if (lesson == null) {
            throw new BizException("未知课次");
        }

        return SignStateEnum.NORMAL;

        // 以下放弃迟到逻辑，默认都是已签到状态

        // 只能签到今天的课：
//        if(!LocalDate.now().isEqual(lesson.getDate())) {
//            throw new BizException("只能签到今天的课次，请联系老师补签");
//        }
//        if(LocalTime.now().isBefore(lesson.getStartTime())) {
//            state = SignStateEnum.NORMAL;
//        } else {
//            state = SignStateEnum.LATE;
//        }

//        // 不是今天的也可以签到：
//        if (LocalDate.now().isBefore(lesson.getDate())) {
//            state = SignStateEnum.NORMAL;
//        } else if (LocalDate.now().isAfter(lesson.getDate())) {
//            state = SignStateEnum.LATE;
//        } else {
//            if (LocalTime.now().isBefore(lesson.getStartTime())) {
//                state = SignStateEnum.NORMAL;
//            } else {
//                state = SignStateEnum.LATE;
//            }
//        }
//        return state;
    }

//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public void generateLessonStudent(Integer minutes) {
//        // 找到时间分钟范围内开课的课次
//        List<Lesson> list = lessonMapper.cronLessonToGenerateStudentRel(minutes);
//        for (Lesson les : list) {
//
//            // 按班级找到没有生成签到记录的班级学生关联数据
//            List<ClassStudent> csList = classStudentMapper.selectClassStudentsByLessonId(les.getId(), false);
//            if (csList == null || csList.size() == 0) {
//                continue;
//            }
//
//            // 生成签到记录
//            List<LessonStudent> insertList = new ArrayList<>();
//            for (ClassStudent cs : csList) {
//                LessonStudent newLs = new LessonStudent();
//                newLs.setLessonId(les.getId());
//                newLs.setTeacherId(null);
//                newLs.setClassId(cs.getClassId());
//                newLs.setStudentId(cs.getStudentId());
//                insertList.add(newLs);
//            }
//            if (insertList.size() > 0) {
//                lessonStudentService.saveBatch(insertList);
//            }
//            log.info("生成签到记录数：{}", insertList.size());
//        }
//    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void closeLesson() {
        List<Lesson> list = lessonMapper.cronLessonToClose();
        if (list != null && list.size() > 0) {
            List<Long> ids = new ArrayList<>();
            for (Lesson les : list) {
                ids.add(les.getId());
            }
            lessonMapper.updateStateBatch(ids, 2);
        }
    }

    @Override
    public void exportStatisData(LessonParamDTO param) {
        List<LessonTeacherStatsVO> list = this.baseMapper.statsByTeachers(param);
        LinkedHashMap<String, String> header = new LinkedHashMap<String, String>() {{
            put("year", "年份");
            put("month", "月份");
            put("teacherName", "姓名");
            put("classFee", "单节上课费");
            put("teacherCount", "上课课次数");
            put("totalClassFee", "上课费小计");
            put("assistantFee", "单节助教费");
            put("assistantCount", "助教课次数");
            put("totalAssistantFee", "助教费小计");
            put("totalFee", "合计");
        }};
        importExportService.exportExcel(header, list, "老师课时费统计");
    }

    @Override
    public void exportStatisDecLesson(LessonParamDTO param) {
        List<LessonCountDecreaseStatsVO> list = this.baseMapper.statisDecLesson(param);
        LinkedHashMap<String, String> header = new LinkedHashMap<String, String>() {{
            put("year", "年份");
            put("month", "月份");
            put("teacherName", "老师名");
            put("studentName", "学生名");
            put("courseName", "课程名");
            put("decCountSum", "消课数");
            put("courseSalary", "单节课酬");
            put("totalSalary", "小计");
        }};
        importExportService.exportExcel(header, list, "老师月消课明细");
    }

    @Override
    public void exportLessonData(LessonParamDTO param) {
        List<LessonVO> list = getAll(param);
        LinkedHashMap<String, String> header = new LinkedHashMap<String, String>() {{
            put("date", "上课日期");
            put("startTime", "开始时间");
            put("endTime", "结束时间");
            put("title", "标题");
            put("courseName", "课程");
            put("className", "班级名称");
            put("lessonType", "类型");
            put("teacherNames", "上课老师");
            put("assistantNames", "助教");
            put("classroom", "教室");
            put("studentNum", "学生数");
            put("studentSignNum", "签到数");
            put("decCount", "消课基数");
            put("state", "状态");
        }};
        importExportService.exportExcel(header, list, "导出课表");
    }

    @Override
    public void lessonNotice() {
        // 找到明天上课的课次
        QueryWrapper<Lesson> qw = new QueryWrapper<>();
        qw.eq("date", LocalDate.now().plusDays(1)).eq("state", LessonStateEnum.UNDERWAY.getCode());
        List<Lesson> list = this.list(qw);

        if (list == null || list.size() == 0) {
            return;
        }

        for (Lesson l : list) {
            List<Staff> teachers = getTeachers(l);
            List<Student> students = getStudents(l);
            Clazz clazz = clazzService.getById(l.getClassId());
            Classroom classroom = classroomService.getById(l.getRoomId());

            LessonStartBO bo = new LessonStartBO();
            bo.setCourseName(l.getTitle());
            bo.setDate(l.getDate().toString());
            bo.setStartTime(l.getStartTime().toString());
            bo.setEndTime(l.getEndTime().toString());
            bo.setSubject("上课通知");

            if (clazz != null) {
                bo.setClassName(clazz.getName());
            } else {
                bo.setClassName("");
            }
            if (classroom != null) {
                bo.setClassroom(classroom.getName());
                bo.setClassroom("");
            }

            if (teachers != null && teachers.size() > 0) {
                for (Staff t : teachers) {
                    bo.setStudentName("");
                    bo.setTeacherName(t.getName());
                    bo.setContent(t.getName() + "老师你好，请按时上课！");
                    notificationService.sendToTeacher(NoticeCodeEnum.TEACHER_LESSON_START, bo, t);
                }
            }

            if (students != null && students.size() > 0) {
                for (Student s : students) {
                    bo.setStudentName(s.getName());
                    bo.setTeacherName("");
                    bo.setContent(s.getName() + "同学你好，请按时上课！");
                    notificationService.sendToStudent(NoticeCodeEnum.STUDENT_LESSON_START, bo, s);
                }
            }

        }
    }

    @Override
    public void lessonLessWarning() {
        List<StudentCourse> list = studentCourseService.getWarningList();
        if (list == null || list.size() == 0) {
            return;
        }
        for (StudentCourse l : list) {
            // 增加已提醒次数
            l.setWarningTimes(l.getWarningTimes() == null ? 1 : l.getWarningTimes() + 1);
            studentCourseService.updateById(l);

            Course course = courseService.getById(l.getCourseId());
            Student student = studentService.getById(l.getStudentId());
            if (student == null) {
                continue;
            }

            LessonNotEnoughBO bo = new LessonNotEnoughBO();
            bo.setCourseName(course.getName());
            bo.setLessonCount(String.valueOf(studentCourseService.getLessonRemainingCount(l)));
            bo.setExpireDate(l.getExpireDate().toString());
            bo.setStudentName(student.getName());
            bo.setTeacherName("");
            bo.setSubject("课时不足通知");
            bo.setContent(student.getName() + "你好，你的课程《" + course.getName() + "》课时不足");

            notificationService.sendToStudent(NoticeCodeEnum.STUDENT_LESSON_COUNT_LESS, bo, student);

            Staff staff = staffService.getById(student.getCounselor());
            if (staff == null) {
                continue;
            }
            bo.setTeacherName(staff.getName());
            bo.setStudentName(student.getName());
            bo.setContent(staff.getName() + "老师你好，学生" + student.getName() + "的课程《" + course.getName() + "》课时不足，请留意。");
            notificationService.sendToTeacher(NoticeCodeEnum.TEACHER_STUDENT_LESSONLESS, bo, staff);
        }
    }

    @Override
    public Boolean studentLeaveApply(Long studentId, Long lessonId) {

        Lesson lesson = getById(lessonId);
        Student student = studentService.getById(studentId);

        QueryWrapper<LessonStudent> qw = new QueryWrapper<>();
        qw.eq("lesson_id", lessonId).eq("student_id", studentId);
        LessonStudent one = lessonStudentService.getOne(qw);
        if (one == null) {
            one = new LessonStudent();
            one.setLessonId(lessonId);
            one.setClassId(lesson.getClassId());
            one.setStudentId(studentId);
            one.setDecLessonCount(0);
            one.setSignTime(LocalDateTime.now());
            one.setSignType(SignTypeEnum.STUDENT);
            one.setSignState(SignStateEnum.LEAVE);
            one.setCounselor(student.getCounselor());
            one.setLessonCount(lesson.getDecCount());
            if (!lessonStudentService.save(one)) {
                return false;
            }
        } else {
            one.setSignTime(LocalDateTime.now());
            one.setSignType(SignTypeEnum.STUDENT);
            one.setSignState(SignStateEnum.LEAVE);
            one.setLessonCount(lesson.getDecCount());
            if (!lessonStudentService.updateById(one)) {
                return false;
            }
        }
        // 通知拿到了学生申请流程里
        // studentLeaveNoticeToTeacher(lesson, student);
        return true;
    }

    @Override
    public boolean changeCourseAtSign(Long lessonId, Long studentId, Long courseId) {
        Lesson lesson = getById(lessonId);
        QueryWrapper<LessonStudent> qw = new QueryWrapper<>();
        qw.eq("lesson_id", lessonId).eq("student_id", studentId);
        LessonStudent one = lessonStudentService.getOne(qw);
        Student student = studentService.getById(studentId);
        if (one == null) {
            one = new LessonStudent();
            one.setLessonId(lessonId);
            one.setStudentId(studentId);
            one.setClassId(lesson.getClassId());
            one.setConsumeCourseId(courseId);
            one.setCounselor(student.getCounselor());
            one.setLessonCount(lesson.getDecCount());
            return lessonStudentService.save(one);
        } else {
            if (!SignStateEnum.NONE.equals(one.getSignState())) {
                throw new BizException("仅未签到的课才可更换课程");
            }
            if (one.getDecLessonCount() != null && one.getDecLessonCount() > 0) {
                throw new BizException("已消课的无法更改消费课程");
            }
            one.setConsumeCourseId(courseId);
            return lessonStudentService.updateById(one);
        }
    }

    @Override
    public boolean teachEvaluate(TeachEvaluateDTO dto, Long studentId) {

        Lesson lesson = getById(dto.getLessonId());
        List<Staff> teachers = getTeachers(lesson);

        List<TeachEvaluation> list = new ArrayList<>();
        for (Staff teacher : teachers) {
            TeachEvaluation item = new TeachEvaluation();
            BeanUtils.copyProperties(dto, item);

            item.setAddTime(LocalDateTime.now());
            item.setTeacherId(teacher.getId());
            item.setStudentId(studentId);
            item.setOrgId(teacher.getOrgId());
            list.add(item);
        }
        QueryWrapper<TeachEvaluation> qw = new QueryWrapper<>();
        qw.eq("lesson_id", dto.getLessonId()).eq("student_id", studentId);
        teachEvaluationService.remove(qw);

        return teachEvaluationService.saveBatch(list);
    }

    @Override
    public boolean addStudents(LessonStudentAddDTO dto, Long currentUserId) {
        List<LessonStudent> list = new ArrayList<>();
        for (Long studentId : dto.getStudentIds()) {
            QueryWrapper<LessonStudent> qw = new QueryWrapper<>();
            qw.eq("lesson_id", dto.getLessonId()).eq("student_id", studentId).last("limit 1");
            LessonStudent item = lessonStudentService.getOne(qw);
            Student student = studentService.getById(studentId);
            if (item == null) {
                Lesson lesson = lessonMapper.selectById(dto.getLessonId());
                LessonStudent ls = new LessonStudent();
                ls.setLessonId(dto.getLessonId());
                ls.setStudentId(studentId);
                ls.setConsumeCourseId(getCourseIdByStudentIdAndLesson(studentId, lesson));
                ls.setSignState(SignStateEnum.NONE);
                ls.setCounselor(student.getCounselor());
                ls.setLessonCount(lesson.getDecCount());
                list.add(ls);
            }
        }
        return list.size() > 0 && lessonStudentService.saveBatch(list);
    }

    /**
    * 根据课程和学生id，获取学生可以消费的课程ID
    * */
    private Long getCourseIdByStudentIdAndLesson(Long studentId, Lesson lesson) {
        StudentCourse sc = studentCourseService.getEnoughLessonCount(lesson.getCourseId(), studentId, lesson.getDecCount());
        if(sc == null) {
            return null;
        }
        return sc.getCourseId();
    }

    @Override
    public boolean handleBooking(List<Long> ids, Boolean state) {
        List<Lesson> lessonList = listByIds(ids);

        // 删除签到记录
        for (Lesson lesson : lessonList) {
           lesson.setBookable(state);
        }
        return updateBatchById(lessonList);
    }

    @Override
    public Boolean studentAppoint(Long lessonId, Long studentId) {

        Lesson lesson = this.getById(lessonId);
        if(!BooleanUtil.isTrue(lesson.getBookable())) {
            throw new BizException("本课次不支持预约");
        }

        // 检查有没有试听卡，如果有，直接消费一次
        CourseTrialRecord ctrRecords = null;
        if(lesson.getCourseId() != null) {
            LambdaQueryWrapper<CourseTrialRecord> wrapper = Wrappers.lambdaQuery();
            wrapper.eq(CourseTrialRecord::getStudentId, studentId);
            wrapper.eq(CourseTrialRecord::getCourseId, lesson.getCourseId());
            wrapper.gt(CourseTrialRecord::getExpiredDate, LocalDateTime.now());
            wrapper.gt(CourseTrialRecord::getLessonCount, 0);
            wrapper.last("limit 1");
            ctrRecords = courseTrialRecordMapper.selectOne(wrapper);
            if(!Objects.isNull(ctrRecords)) {
                ctrRecords.setLessonCount(ctrRecords.getLessonCount() - 1);
                courseTrialRecordMapper.updateById(ctrRecords);
            }
        }

        Student student = studentService.getById(studentId);

        appointmentService.addOne(student, lesson, ctrRecords);

        if(settingService.boolValue(SettingNameEnum.AUTO_JOIN_LESSON_BY_APPOINTMENT.getCode())) {
            lessonStudentService.addRecord(lessonId, student, SignStateEnum.NONE);
        }
        String mobile = studentService.getMobile(student);
        messageService.sendToStaff(
                studentId,
                MessageUserTypeEnum.STUDENT,
                lesson.getTeacherId(),
                "你的课程有新的预约",
                student.getName() + "(" + mobile + ") 预约了你的课程:" + lesson.descToString());

        Staff toStaff = staffService.getById(lesson.getTeacherId());
        LessonChangeBO bo = new LessonChangeBO();
        bo.setLessonTitle(lesson.getTitle());
        bo.setStudentName(student.getName());
        bo.setContent(student.getName() + "预约了一节课，请注意安排。");
        notificationService.sendToTeacher(NoticeCodeEnum.TEACHER_NEW_APPOINTMENT, bo, toStaff);
        return true;
    }

    @Override
    public void studentCanSignLogic(List<LessonVO> records) {
        // 学生是否可以签到
        boolean canSign = BooleanUtil.isTrue(settingService.boolValue(SettingNameEnum.STUDENT_CENTER_CAN_SIGN.getCode()));
        // 是否可以签今天之前的
        boolean canSignWholeDay = BooleanUtil.isTrue(settingService.boolValue(SettingNameEnum.STUDENT_CAN_SIGN_WHOLE_DAY.getCode()));
        for(LessonVO vo : records) {
            LocalDateTime lessonEndTime = LocalDateTime.of(vo.getDate(), vo.getEndTime());
            // 时间是否允许：若可以签今天之前的，则 课程结束前的都能签；否则，必须是当天 且 课没结束
            boolean timeLimit = canSignWholeDay ? LocalDate.now().isEqual(vo.getDate()) : (LocalDate.now().isEqual(vo.getDate()) && LocalDateTime.now().isBefore(lessonEndTime));
            // 可签条件: 系统设置可签到，并且，当天的课程，并且，在课时结束时间之前，并且，课程是未签到状态
            vo.setStudentCanSign(
                LessonStateEnum.UNDERWAY.equals(vo.getState())
                    && canSign
                    && timeLimit
                    && SignStateEnum.NONE.equals(vo.getStudentSignState()));
        }
    }

    /**
    *  只能修改： changeDays, startTime, endTime, roomId
    * */
    @Override
    public boolean updateLessonBatch(LessonSaveDTO dto) {

        if(dto.getUpdateIds() == null || dto.getUpdateIds().length == 0 ) {
            throw new BizException("未选择课程");
        }

        List<Lesson> lessons = this.listByIds(Arrays.asList(dto.getUpdateIds()));
        if(CollectionUtils.isEmpty(lessons)) {
            throw new BizException("无效课程");
        }

        if(dto.getChangeDays()!= null && Math.abs(dto.getChangeDays()) > 7) {
            throw new BizException("最多可以增减7天");
        }

        if (dto.getStartTime()!= null && dto.getEndTime()!= null && dto.getStartTime().isAfter(dto.getEndTime())) {
            throw new BizException("上课时间设置有误");
        }

        if (dto.getStartTime()== null && dto.getEndTime()!= null ) {
            throw new BizException("缺少上课时间");
        }

        if (dto.getStartTime()!= null && dto.getEndTime()== null ) {
            throw new BizException("缺少下课时间");
        }

        Long mainTeacherId = null;
        if(CollectionUtils.isNotEmpty(dto.getTeacherIds())) {
            mainTeacherId = dto.getTeacherIds().get(0);
        }

        for(Lesson lesson : lessons) {
            boolean changed = false;
            if(dto.getChangeDays()!= null && dto.getChangeDays() != 0) {
                lesson.setDate(lesson.getDate().plusDays(dto.getChangeDays()));
                changed = true;
            }
            if(dto.getStartTime()!= null && dto.getEndTime()!= null ) {
                lesson.setStartTime(dto.getStartTime());
                lesson.setEndTime(dto.getEndTime());
                changed = true;
            }
            if(dto.getRoomId()!= null) {
                lesson.setRoomId(dto.getRoomId());
                changed = true;
            }

            List<LessonTeacher> teacherList = new ArrayList<>();
            if(mainTeacherId != null) {
                lesson.setTeacherId(mainTeacherId);
                for (Long teacherId : dto.getTeacherIds()) {
                    LessonTeacher lt = new LessonTeacher();
                    lt.setTeacherId(teacherId);
                    lt.setLessonId(lesson.getId());
                    lt.setTypeNum(TeacherTypeEnum.TEACHER.getCode());
                    teacherList.add(lt);
                }
                changed = true;
            }

            if (CollectionUtils.isNotEmpty(dto.getAssistantIds())) {
                for (Long teacherId : dto.getAssistantIds()) {
                    LessonTeacher lt = new LessonTeacher();
                    lt.setTeacherId(teacherId);
                    lt.setLessonId(lesson.getId());
                    lt.setTypeNum(TeacherTypeEnum.ASSISTANT.getCode());
                    teacherList.add(lt);
                }
                changed = true;
            }

            if(!changed) {
                throw new BizException("无变化");
            }

            if(CollectionUtils.isNotEmpty(teacherList)) {
                if (mainTeacherId != null) {
                    lessonTeacherService.deleteByLessonId(lesson.getId(), 1);
                }
                if (CollectionUtils.isNotEmpty(dto.getAssistantIds())) {
                    lessonTeacherService.deleteByLessonId(lesson.getId(), 2);
                }
                lessonTeacherService.saveBatch(teacherList);
            }

            // 检查冲突 批量修改不检查冲突，人为检查即可，有的课不需要检查
//            LessonDatetimeAndTeacherBO bo = new LessonDatetimeAndTeacherBO();
//            bo.setDate(lesson.getDate());
//            bo.setTeacherId(lesson.getTeacherId());
//            bo.setStartTime(dto.getStartTime());
//            bo.setEndTime(dto.getEndTime());
//            bo.setLessonId(dto.getId());
//            checkConflictByDO(bo);
        }

        return updateBatchById(lessons);
    }

    @Override
    public List<Map<String, Object>> getLessonNumEveryDay(LessonParamDTO param) {
        return lessonMapper.getLessonNumEveryDay(param);
    }

    @Override
    public boolean checkTeacherSignLimit(Lesson lesson, SignStateEnum state, boolean throwExp) {

        // 签到和补签的才验证 （弃用）
//        if(!SignStateEnum.NORMAL.equals(state) && !SignStateEnum.LATE.equals(state)) {
//            return true;
//        }

        int beforeHours = settingService.intValue(SettingNameEnum.TEACHER_SIGN_LIMIT_BEFORE_LESSON.getCode());
        int afterHours = settingService.intValue(SettingNameEnum.TEACHER_SIGN_LIMIT_AFTER_LESSON.getCode());

        LocalDateTime lessonStartTime = LocalDateTime.of(lesson.getDate(), lesson.getStartTime());
        LocalDateTime lessonEndTime = LocalDateTime.of(lesson.getDate(), lesson.getEndTime());

        if(beforeHours != 0) {
            LocalDateTime beforeDateTime = lessonStartTime.minusHours(beforeHours);
            if(LocalDateTime.now().isBefore(beforeDateTime)) {
                if(throwExp) {
                    throw new BizException("无法点名：未到点名时间");
                }
                return false;
            }
        }

        if(afterHours != 0) {
            LocalDateTime afterDateTime = lessonEndTime.plusHours(afterHours);
            if(LocalDateTime.now().isAfter(afterDateTime)) {
                if(throwExp) {
                    throw new BizException("无法点名：已超过点名时间");
                }
                return false;
            }
        }

        return true;
    }
}
